# /**
# * Copyright (c) 2022 Beijing Jiaotong University
# * PhotLab is licensed under [Open Source License].
# * You can use this software according to the terms and conditions of the [Open Source License].
# *
# * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
# *
# * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# *
# * See the [Open Source License] for more details.
# */
# /**
# * Author: Shihao Xie
# * Created: Apr. 17, 2023
# * Supported by: National Key Research and Development Program of China
# */

from phot_server.core.exception import IdDuplicationException, ResourceNotFoundError
from phot_server.rpc.graph_data_type.ttypes import RenderGraph, RenderRect, RenderGraphNode, RenderRoute, \
    RenderTextArea
from phot_server.repository.phot_graph_repository import monitor_graph_change
from phot_server.utils.singleton import singleton


# temporarily storage graph render data
@singleton
class RenderGraphRepository:

    def __init__(self):
        self.__render_graphs = {}

    def add_render_graph(self, render_graph: RenderGraph):
        """
        This method is to add a render_graph to container.
        :param render_graph: render_graph object
        :return: RenderGraph object that be added
        """
        if self.has_render_graph(render_graph.graphId):
            raise IdDuplicationException(RenderGraph.__name__, render_graph.graphId)
        self.__render_graphs[render_graph.graphId] = render_graph
        return render_graph

    def get_render_graph(self, graph_id) -> RenderGraph:
        """
        This method is to get a graph in graph container
        :param graph_id: the name of graph in graph container
        :return: RenderGraph object that contain graph data
        """
        graph = self.__render_graphs.get(graph_id, None)
        if graph is None:
            raise ResourceNotFoundError(RenderGraph.__name__, graph_id)
        else:
            return graph

    def has_render_graph(self, graph_id) -> bool:
        return True if self.__render_graphs.get(graph_id) is not None else False

    def delete_render_graph(self, graph_id):
        """
        This method is to delete a graph in graph container by graph name
        :param graph_id: the name of graph in graph container
        :return: RenderGraph object that being deleted
        """
        if self.has_render_graph(graph_id):
            self.__render_graphs.pop(graph_id)
        else:
            raise ResourceNotFoundError(RenderGraph.__name__, graph_id)

    def set_render_graph(self, graph_id, graph: RenderGraph):
        """
        This method is to get a graph in graph container
        :param graph: new graph
        :param graph_id: the name of graph in graph container
        :return: RenderGraph object that contain graph data
        """
        self.delete_render_graph(graph_id)
        return self.add_render_graph(graph)

    def rename_render_graph(self, graph_id, new_name):
        render_graph = self.get_render_graph(graph_id)
        render_graph.graphName = new_name

    # node
    @monitor_graph_change()
    def add_node(self, graph_id, node: RenderGraphNode):
        """
        This method is to add a node in a graph
        :param graph_id: the name of graph in graph container
        :param node: RenderGraphNode object to be added
        :return: RenderGraphNode object of being added one
        """
        if self.has_node(graph_id, node.nodeId):
            raise IdDuplicationException(RenderGraphNode.__name__, node.nodeId, RenderGraph.__name__, graph_id)
        self.get_render_graph(graph_id).nodes.append(node)
        return node

    def get_node(self, graph_id, node_id, raise_error=True):
        """
        This method is to get a node in a graph
        :param graph_id: the name of graph in graph container
        :param node_id: the id of node
        :param raise_error: return None or raise error if it has no match node
        :return: RenderGraphNode object
        """
        graph = self.get_render_graph(graph_id)
        for node in graph.nodes:
            if node.nodeId == node_id:
                return node
        if raise_error:
            raise ResourceNotFoundError(RenderGraphNode.__name__, node_id, RenderGraph.__name__, graph_id)
        return None

    def has_node(self, graph_id, node_id):
        return True if self.get_node(graph_id, node_id, False) is not None else False

    def get_nodes(self, graph_id) -> list:
        """
        This method is to get all nodes in a graph
        :param graph_id: the name of graph in graph container
        :return: RenderGraphNode object map
        """
        return self.get_render_graph(graph_id).nodes

    @monitor_graph_change()
    def alter_node(self, graph_id, node: RenderGraphNode):
        """
        This method is to alter a node in a graph
        :param graph_id: the name of graph in graph container
        :param node_id: the id of node
        :param node: RenderGraphNode object to alter
        :return: new RenderGraphNode object
        """
        graph = self.get_render_graph(graph_id)
        self.delete_node(graph_id, node.nodeId)
        graph.nodes.append(node)

    @monitor_graph_change()
    def delete_node(self, graph_id, node_id):
        """
        This method is to delete a node in a graph
        :param graph_id: the name of graph in graph container
        :param node_id: the id of node
        :return: RenderGraphNode object to be deleted
        """
        graph = self.get_render_graph(graph_id)
        target = self.get_node(graph_id, node_id)
        graph.nodes.remove(target)

    @monitor_graph_change()
    def set_node_rect(self, graph_id, node_id, rect: RenderRect):
        self.get_node(graph_id, node_id).rect = rect

    @monitor_graph_change()
    def set_node_point(self, graph_id, node_id, x, y):
        node = self.get_node(graph_id, node_id)
        node.rect.x = x
        node.rect.y = y

    # route
    def get_route(self, graph_id, route_id, raise_error=True) :
        graph = self.get_render_graph(graph_id)
        for route in graph.routes:
            if route.routeId == route_id:
                return route
        if raise_error:
            raise ResourceNotFoundError(RenderRoute.__name__, route_id, RenderGraph.__name__, graph_id)
        return None

    def has_route(self, graph_id, route_id):
        return True if self.get_route(graph_id, route_id, False) is not None else False

    def get_routes(self, graph_id):
        """
        This method is to get all route_list in a graph
        :param graph_id: the name of graph in graph container
        :return: PhotLink list
        """
        return self.get_render_graph(graph_id).routes

    @monitor_graph_change()
    def add_route(self, graph_id, route: RenderRoute):
        """
        This method is to add a route in a graph
        :param graph_id: the name of graph in graph container
        :param route: PhotLink object that contain route data
        :return: PhotLink object been added
        """
        if self.has_route(graph_id, route.routeId) is False:
            self.get_render_graph(graph_id).routes.append(route)
            return route
        else:
            raise IdDuplicationException(RenderRoute.__name__, route.routeId, RenderGraph.__name__, graph_id)

    @monitor_graph_change()
    def alter_route(self, graph_id, route: RenderRoute):
        """
        This method is to alter all route_list in a graph
        :param route: route alter to
        :param graph_id: the name of graph in graph container
        :return: new route list
        """
        self.delete_route(graph_id, route.routeId)
        return self.add_route(graph_id, route)

    @monitor_graph_change()
    def alter_route_list(self, graph_id, route_list: list):
        """
        This method is to alter all route_list in a graph
        :param graph_id: the name of graph in graph container
        :param route_list: route list
        :return: new route list
        """
        self.get_render_graph(graph_id).routes = route_list

    @monitor_graph_change()
    def delete_route(self, graph_id, route_id):
        """
        This method is to delete a route in a graph
        :param graph_id: the name of graph in graph container
        :param route_id: route to be deleted
        :return: the route been deleted
        """
        graph = self.get_render_graph(graph_id)
        target = self.get_route(graph_id, route_id)
        graph.routes.remove(target)

    @monitor_graph_change()
    def set_route_path(self, graph_id, route_id, path: list):
        self.get_route(graph_id, route_id).path = path

    def get_text_area(self, graph_id, text_id, raise_error=True) :
        graph = self.get_render_graph(graph_id)
        for text_area in graph.texts:
            if text_area.textId == text_id:
                return text_area
        if raise_error:
            raise ResourceNotFoundError(RenderTextArea.__name__, text_id, RenderGraph.__name__, graph_id)
        return None

    def has_text_area(self, graph_id, text_id):
        return True if self.get_text_area(graph_id, text_id, False) is not None else False

    def get_text_area_list(self, graph_id):
        """
        This method is to get all texts in a graph
        :param graph_id: the name of graph in graph container
        :return: PhotLink list
        """
        return self.get_render_graph(graph_id).texts

    @monitor_graph_change()
    def add_text_area(self, graph_id, text_area: RenderTextArea):
        """
        This method is to add a text in a graph
        :param graph_id: the name of graph in graph container
        :param text_area: PhotLink object that contain text data
        :return: PhotLink object been added
        """
        if self.has_text_area(graph_id, text_area.textId) is False:
            self.get_render_graph(graph_id).texts.append(text_area)
            return text_area
        else:
            raise IdDuplicationException(RenderTextArea.__name__, text_area.textId, RenderGraph.__name__, graph_id)

    @monitor_graph_change()
    def alter_text_area(self, graph_id, text_area: RenderTextArea):
        """
        This method is to alter all texts in a graph
        :param text_area: text alter to
        :param graph_id: the name of graph in graph container
        :return: new text list
        """
        self.delete_text_area(graph_id, text_area.textId)
        return self.add_text_area(graph_id, text_area)

    @monitor_graph_change()
    def alter_text_area_list(self, graph_id, text_area_list: list):
        """
        This method is to alter all texts in a graph
        :param graph_id: the name of graph in graph container
        :param text_area_list: text list
        :return: new text list
        """
        self.get_render_graph(graph_id).texts = text_area_list

    @monitor_graph_change()
    def delete_text_area(self, graph_id, text_id):
        """
        This method is to delete a text in a graph
        :param graph_id: the name of graph in graph container
        :param text_id: text to be deleted
        :return: the text been deleted
        """
        graph = self.get_render_graph(graph_id)
        target = self.get_text_area(graph_id, text_id)
        graph.texts.remove(target)

    @monitor_graph_change()
    def set_text_area_rect(self, graph_id, text_id, rect: RenderRect):
        self.get_text_area(graph_id, text_id).rect = rect

    @monitor_graph_change()
    def set_text(self, graph_id, text_id, text):
        self.get_text_area(graph_id, text_id).text = text
